/*
 * Copyright 2024 iLogtail Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "models/SpanEvent.h"

#include <algorithm>

#include "constants/SpanConstants.h"

using namespace std;

namespace logtail {

void SpanEvent::SpanLink::SetTraceId(const string& traceId) {
    const StringBuffer& b = GetSourceBuffer()->CopyString(traceId);
    mTraceId = StringView(b.data, b.size);
}

void SpanEvent::SpanLink::SetSpanId(const string& spanId) {
    const StringBuffer& b = GetSourceBuffer()->CopyString(spanId);
    mSpanId = StringView(b.data, b.size);
}

void SpanEvent::SpanLink::SetTraceState(const string& traceState) {
    const StringBuffer& b = GetSourceBuffer()->CopyString(traceState);
    mTraceState = StringView(b.data, b.size);
}

StringView SpanEvent::SpanLink::GetTag(StringView key) const {
    auto it
        = std::find_if(mTags.mInner.begin(), mTags.mInner.end(), [key](const auto& item) { return item.first == key; });
    if (it != mTags.mInner.end()) {
        return it->second;
    }
    return gEmptyStringView;
}

bool SpanEvent::SpanLink::HasTag(StringView key) const {
    return std::find_if(mTags.mInner.begin(), mTags.mInner.end(), [key](const auto& item) { return item.first == key; })
        != mTags.mInner.end();
}

void SpanEvent::SpanLink::SetTag(StringView key, StringView val) {
    SetTagNoCopy(GetSourceBuffer()->CopyString(key), GetSourceBuffer()->CopyString(val));
}

void SpanEvent::SpanLink::SetTag(const string& key, const string& val) {
    SetTagNoCopy(GetSourceBuffer()->CopyString(key), GetSourceBuffer()->CopyString(val));
}

void SpanEvent::SpanLink::SetTagNoCopy(const StringBuffer& key, const StringBuffer& val) {
    SetTagNoCopy(StringView(key.data, key.size), StringView(val.data, val.size));
}

void SpanEvent::SpanLink::SetTagNoCopy(StringView key, StringView val) {
    mTags.Insert(key, val);
}

void SpanEvent::SpanLink::AppendTag(StringView key, StringView val) {
    AppendTagNoCopy(GetSourceBuffer()->CopyString(key), GetSourceBuffer()->CopyString(val));
}

void SpanEvent::SpanLink::AppendTag(const string& key, const string& val) {
    AppendTagNoCopy(GetSourceBuffer()->CopyString(key), GetSourceBuffer()->CopyString(val));
}

void SpanEvent::SpanLink::AppendTagNoCopy(const StringBuffer& key, const StringBuffer& val) {
    AppendTagNoCopy(StringView(key.data, key.size), StringView(val.data, val.size));
}

void SpanEvent::SpanLink::AppendTagNoCopy(StringView key, StringView val) {
    mTags.Append(key, val);
}

void SpanEvent::SpanLink::DelTag(StringView key) {
    mTags.Erase(key);
}

shared_ptr<SourceBuffer>& SpanEvent::SpanLink::GetSourceBuffer() {
    return mParent->GetSourceBuffer();
}

size_t SpanEvent::SpanLink::DataSize() const {
    return mTraceId.size() + mSpanId.size() + mTraceState.size() + mTags.DataSize();
}

Json::Value SpanEvent::SpanLink::ToJson() const {
    Json::Value root;
    root[DEFAULT_TRACE_TAG_TRACE_ID] = mTraceId.to_string();
    root[DEFAULT_TRACE_TAG_SPAN_ID] = mSpanId.to_string();
    if (!mTraceState.empty()) {
        root[DEFAULT_TRACE_TAG_TRACE_STATE] = mTraceState.to_string();
    }
    if (!mTags.mInner.empty()) {
        Json::Value& tags = root[DEFAULT_TRACE_TAG_ATTRIBUTES];
        for (const auto& tag : mTags.mInner) {
            tags[tag.first.to_string()] = tag.second.to_string();
        }
    }
    return root;
}

#ifdef APSARA_UNIT_TEST_MAIN
void SpanEvent::SpanLink::FromJson(const Json::Value& value) {
    SetTraceId(value[DEFAULT_TRACE_TAG_TRACE_ID].asString());
    SetSpanId(value[DEFAULT_TRACE_TAG_SPAN_ID].asString());
    if (value.isMember(DEFAULT_TRACE_TAG_TRACE_STATE)) {
        string s = value[DEFAULT_TRACE_TAG_TRACE_STATE].asString();
        if (!s.empty()) {
            SetTraceState(s);
        }
    }
    if (value.isMember(DEFAULT_TRACE_TAG_ATTRIBUTES)) {
        Json::Value tags = value[DEFAULT_TRACE_TAG_ATTRIBUTES];
        for (const auto& key : tags.getMemberNames()) {
            SetTag(key, tags[key].asString());
        }
    }
}
#endif

void SpanEvent::InnerEvent::SetName(const string& name) {
    const StringBuffer& b = GetSourceBuffer()->CopyString(name);
    mName = StringView(b.data, b.size);
}

StringView SpanEvent::InnerEvent::GetTag(StringView key) const {
    auto it
        = std::find_if(mTags.mInner.begin(), mTags.mInner.end(), [key](const auto& item) { return item.first == key; });
    if (it != mTags.mInner.end()) {
        return it->second;
    }
    return gEmptyStringView;
}

bool SpanEvent::InnerEvent::HasTag(StringView key) const {
    return std::find_if(mTags.mInner.begin(), mTags.mInner.end(), [key](const auto& item) { return item.first == key; })
        != mTags.mInner.end();
}

void SpanEvent::InnerEvent::SetTag(StringView key, StringView val) {
    SetTagNoCopy(GetSourceBuffer()->CopyString(key), GetSourceBuffer()->CopyString(val));
}

void SpanEvent::InnerEvent::SetTag(const string& key, const string& val) {
    SetTagNoCopy(GetSourceBuffer()->CopyString(key), GetSourceBuffer()->CopyString(val));
}

void SpanEvent::InnerEvent::SetTagNoCopy(const StringBuffer& key, const StringBuffer& val) {
    SetTagNoCopy(StringView(key.data, key.size), StringView(val.data, val.size));
}

void SpanEvent::InnerEvent::SetTagNoCopy(StringView key, StringView val) {
    mTags.Insert(key, val);
}

void SpanEvent::InnerEvent::AppendTag(StringView key, StringView val) {
    AppendTagNoCopy(GetSourceBuffer()->CopyString(key), GetSourceBuffer()->CopyString(val));
}

void SpanEvent::InnerEvent::AppendTag(const string& key, const string& val) {
    AppendTagNoCopy(GetSourceBuffer()->CopyString(key), GetSourceBuffer()->CopyString(val));
}

void SpanEvent::InnerEvent::AppendTagNoCopy(const StringBuffer& key, const StringBuffer& val) {
    AppendTagNoCopy(StringView(key.data, key.size), StringView(val.data, val.size));
}

void SpanEvent::InnerEvent::AppendTagNoCopy(StringView key, StringView val) {
    mTags.Append(key, val);
}

void SpanEvent::InnerEvent::DelTag(StringView key) {
    mTags.Erase(key);
}

shared_ptr<SourceBuffer>& SpanEvent::InnerEvent::GetSourceBuffer() {
    return mParent->GetSourceBuffer();
}

size_t SpanEvent::InnerEvent::DataSize() const {
    return sizeof(decltype(mTimestampNs)) + mName.size() + mTags.DataSize();
}

Json::Value SpanEvent::InnerEvent::ToJson() const {
    Json::Value root;
    root[DEFAULT_TRACE_TAG_SPAN_EVENT_NAME] = mName.to_string();
    root[DEFAULT_TRACE_TAG_TIMESTAMP] = static_cast<int64_t>(mTimestampNs);
    if (!mTags.mInner.empty()) {
        Json::Value& tags = root[DEFAULT_TRACE_TAG_ATTRIBUTES];
        for (const auto& tag : mTags.mInner) {
            tags[tag.first.to_string()] = tag.second.to_string();
        }
    }
    return root;
}

#ifdef APSARA_UNIT_TEST_MAIN
void SpanEvent::InnerEvent::FromJson(const Json::Value& value) {
    SetName(value[DEFAULT_TRACE_TAG_SPAN_EVENT_NAME].asString());
    SetTimestampNs(value[DEFAULT_TRACE_TAG_TIMESTAMP].asUInt64());
    if (value.isMember(DEFAULT_TRACE_TAG_ATTRIBUTES)) {
        Json::Value tags = value[DEFAULT_TRACE_TAG_ATTRIBUTES];
        for (const auto& key : tags.getMemberNames()) {
            SetTag(key, tags[key].asString());
        }
    }
}
#endif

const string SpanEvent::OTLP_SCOPE_NAME = "otlp.scope.name";
const string SpanEvent::OTLP_SCOPE_VERSION = "otlp.scope.version";

SpanEvent::SpanEvent(PipelineEventGroup* ptr) : PipelineEvent(Type::SPAN, ptr) {
}

unique_ptr<PipelineEvent> SpanEvent::Copy() const {
    return make_unique<SpanEvent>(*this);
}

void SpanEvent::Reset() {
    PipelineEvent::Reset();
    mTraceId = gEmptyStringView;
    mSpanId = gEmptyStringView;
    mTraceState = gEmptyStringView;
    mParentSpanId = gEmptyStringView;
    mName = gEmptyStringView;
    mKind = Kind::Unspecified;
    mStartTimeNs = 0;
    mEndTimeNs = 0;
    mTags.Clear();
    mEvents.clear();
    mLinks.clear();
    mStatus = StatusCode::Unset;
    mScopeTags.Clear();
}

void SpanEvent::SetTraceId(const string& traceId) {
    const StringBuffer& b = GetSourceBuffer()->CopyString(traceId);
    mTraceId = StringView(b.data, b.size);
}

void SpanEvent::SetSpanId(const string& spanId) {
    const StringBuffer& b = GetSourceBuffer()->CopyString(spanId);
    mSpanId = StringView(b.data, b.size);
}

void SpanEvent::SetTraceState(const string& traceState) {
    const StringBuffer& b = GetSourceBuffer()->CopyString(traceState);
    mTraceState = StringView(b.data, b.size);
}

void SpanEvent::SetParentSpanId(const string& parentSpanId) {
    const StringBuffer& b = GetSourceBuffer()->CopyString(parentSpanId);
    mParentSpanId = StringView(b.data, b.size);
}

void SpanEvent::SetName(const string& name) {
    const StringBuffer& b = GetSourceBuffer()->CopyString(name);
    mName = StringView(b.data, b.size);
}

StringView SpanEvent::GetTag(StringView key) const {
    auto it
        = std::find_if(mTags.mInner.begin(), mTags.mInner.end(), [key](const auto& item) { return item.first == key; });
    if (it != mTags.mInner.end()) {
        return it->second;
    }
    return gEmptyStringView;
}

bool SpanEvent::HasTag(StringView key) const {
    return std::find_if(mTags.mInner.begin(), mTags.mInner.end(), [key](const auto& item) { return item.first == key; })
        != mTags.mInner.end();
}

void SpanEvent::SetTag(StringView key, StringView val) {
    SetTagNoCopy(GetSourceBuffer()->CopyString(key), GetSourceBuffer()->CopyString(val));
}

void SpanEvent::SetTag(const string& key, const string& val) {
    SetTagNoCopy(GetSourceBuffer()->CopyString(key), GetSourceBuffer()->CopyString(val));
}

void SpanEvent::SetTagNoCopy(const StringBuffer& key, const StringBuffer& val) {
    SetTagNoCopy(StringView(key.data, key.size), StringView(val.data, val.size));
}

void SpanEvent::SetTagNoCopy(StringView key, StringView val) {
    mTags.Insert(key, val);
}

void SpanEvent::AppendTag(StringView key, StringView val) {
    AppendTagNoCopy(GetSourceBuffer()->CopyString(key), GetSourceBuffer()->CopyString(val));
}

void SpanEvent::AppendTag(const string& key, const string& val) {
    AppendTagNoCopy(GetSourceBuffer()->CopyString(key), GetSourceBuffer()->CopyString(val));
}

void SpanEvent::AppendTagNoCopy(const StringBuffer& key, const StringBuffer& val) {
    AppendTagNoCopy(StringView(key.data, key.size), StringView(val.data, val.size));
}

void SpanEvent::AppendTagNoCopy(StringView key, StringView val) {
    mTags.Append(key, val);
}

void SpanEvent::DelTag(StringView key) {
    mTags.Erase(key);
}

SpanEvent::InnerEvent* SpanEvent::AddEvent() {
    SpanEvent::InnerEvent e(this);
    mEvents.emplace_back(std::move(e));
    return &mEvents.back();
}

SpanEvent::SpanLink* SpanEvent::AddLink() {
    SpanEvent::SpanLink l(this);
    mLinks.emplace_back(std::move(l));
    return &mLinks.back();
}

StringView SpanEvent::GetScopeTag(StringView key) const {
    auto it = mScopeTags.mInner.find(key);
    if (it != mScopeTags.mInner.end()) {
        return it->second;
    }
    return gEmptyStringView;
}

bool SpanEvent::HasScopeTag(StringView key) const {
    return mScopeTags.mInner.find(key) != mScopeTags.mInner.end();
}

void SpanEvent::SetScopeTag(StringView key, StringView val) {
    SetScopeTagNoCopy(GetSourceBuffer()->CopyString(key), GetSourceBuffer()->CopyString(val));
}

void SpanEvent::SetScopeTag(const string& key, const string& val) {
    SetScopeTagNoCopy(GetSourceBuffer()->CopyString(key), GetSourceBuffer()->CopyString(val));
}

void SpanEvent::SetScopeTagNoCopy(const StringBuffer& key, const StringBuffer& val) {
    SetScopeTagNoCopy(StringView(key.data, key.size), StringView(val.data, val.size));
}

void SpanEvent::SetScopeTagNoCopy(StringView key, StringView val) {
    mScopeTags.Insert(key, val);
}

void SpanEvent::DelScopeTag(StringView key) {
    mScopeTags.Erase(key);
}

size_t SpanEvent::DataSize() const {
    // TODO: this is not O(1), however, these two fields are not frequently used, so it can thought of O(1)
    size_t eventsSize = sizeof(decltype(mEvents));
    for (const auto& item : mEvents) {
        eventsSize += item.DataSize();
    }
    size_t linksSize = sizeof(decltype(mLinks));
    for (const auto& item : mLinks) {
        linksSize += item.DataSize();
    }
    // TODO: for enum, it seems more reasonable to use actual string size instead of size of enum
    return PipelineEvent::DataSize() + mTraceId.size() + mSpanId.size() + mTraceState.size() + mParentSpanId.size()
        + mName.size() + sizeof(decltype(mKind)) + sizeof(decltype(mStartTimeNs)) + sizeof(decltype(mEndTimeNs))
        + mTags.DataSize() + eventsSize + linksSize + sizeof(decltype(mStatus)) + mScopeTags.DataSize();
}

#ifdef APSARA_UNIT_TEST_MAIN
Json::Value SpanEvent::ToJson(bool enableEventMeta) const {
    Json::Value root;
    root["type"] = static_cast<int>(GetType());
    root["timestamp"] = GetTimestamp();
    if (GetTimestampNanosecond()) {
        root["timestampNanosecond"] = static_cast<int32_t>(GetTimestampNanosecond().value());
    }
    root["traceId"] = mTraceId.to_string();
    root["spanId"] = mSpanId.to_string();
    if (!mTraceState.empty()) {
        root["traceState"] = mTraceState.to_string();
    }
    if (!mParentSpanId.empty()) {
        root["parentSpanId"] = mParentSpanId.to_string();
    }
    root["name"] = mName.to_string();
    if (mKind != Kind::Unspecified) {
        root["kind"] = static_cast<int>(mKind);
    }
    // must be int since jsoncpp will take integral as int during parse, which
    // will lead to inequality on json comparison
    root["startTimeNs"] = static_cast<int64_t>(mStartTimeNs);
    root["endTimeNs"] = static_cast<int64_t>(mEndTimeNs);
    if (!mTags.mInner.empty()) {
        Json::Value& tags = root[DEFAULT_TRACE_TAG_ATTRIBUTES];
        for (const auto& tag : mTags.mInner) {
            tags[tag.first.to_string()] = tag.second.to_string();
        }
    }
    if (!mEvents.empty()) {
        Json::Value& events = root[DEFAULT_TRACE_TAG_EVENTS];
        for (const auto& event : mEvents) {
            events.append(event.ToJson());
        }
    }
    if (!mLinks.empty()) {
        Json::Value& links = root[DEFAULT_TRACE_TAG_LINKS];
        for (const auto& link : mLinks) {
            links.append(link.ToJson());
        }
    }
    if (mStatus != StatusCode::Unset) {
        root["status"] = static_cast<int>(mStatus);
    }
    if (!mScopeTags.mInner.empty()) {
        Json::Value& tags = root["scopeTags"];
        for (const auto& tag : mScopeTags.mInner) {
            tags[tag.first.to_string()] = tag.second.to_string();
        }
    }
    return root;
}

bool SpanEvent::FromJson(const Json::Value& root) {
    if (root.isMember("timestampNanosecond")) {
        SetTimestamp(root["timestamp"].asInt64(), root["timestampNanosecond"].asInt64());
    } else {
        SetTimestamp(root["timestamp"].asInt64());
    }
    SetTraceId(root["traceId"].asString());
    SetSpanId(root["spanId"].asString());
    if (root.isMember("traceState")) {
        string s = root["traceState"].asString();
        if (!s.empty()) {
            SetTraceState(s);
        }
    }
    if (root.isMember("parentSpanId")) {
        string s = root["parentSpanId"].asString();
        if (!s.empty()) {
            SetParentSpanId(s);
        }
    }
    SetName(root["name"].asString());
    if (root.isMember("kind")) {
        SetKind(static_cast<Kind>(root["kind"].asInt()));
    }
    SetStartTimeNs(root["startTimeNs"].asUInt64());
    SetEndTimeNs(root["endTimeNs"].asUInt64());
    if (root.isMember(DEFAULT_TRACE_TAG_ATTRIBUTES)) {
        Json::Value tags = root[DEFAULT_TRACE_TAG_ATTRIBUTES];
        for (const auto& key : tags.getMemberNames()) {
            SetTag(key, tags[key].asString());
        }
    }
    if (root.isMember(DEFAULT_TRACE_TAG_EVENTS)) {
        Json::Value events = root[DEFAULT_TRACE_TAG_EVENTS];
        for (const auto& event : events) {
            InnerEvent* e = AddEvent();
            e->FromJson(event);
        }
    }
    if (root.isMember(DEFAULT_TRACE_TAG_LINKS)) {
        Json::Value links = root[DEFAULT_TRACE_TAG_LINKS];
        for (const auto& link : links) {
            SpanLink* l = AddLink();
            l->FromJson(link);
        }
    }
    if (root.isMember("status")) {
        SetStatus(static_cast<StatusCode>(root["status"].asInt()));
    }
    if (root.isMember("scopeTags")) {
        Json::Value tags = root["scopeTags"];
        for (const auto& key : tags.getMemberNames()) {
            SetScopeTag(key, tags[key].asString());
        }
    }
    return true;
}
#endif

const static std::string sSpanStatusCodeUnSet = "UNSET";
const static std::string sSpanStatusCodeOk = "OK";
const static std::string sSpanStatusCodeError = "ERROR";

const std::string& GetStatusString(SpanEvent::StatusCode status) {
    switch (status) {
        case SpanEvent::StatusCode::Unset:
            return sSpanStatusCodeUnSet;
        case SpanEvent::StatusCode::Ok:
            return sSpanStatusCodeOk;
        case SpanEvent::StatusCode::Error:
            return sSpanStatusCodeError;
        default:
            return sSpanStatusCodeUnSet;
    }
}

const static std::string sSpanKindUnspecified = "unspecified";
const static std::string sSpanKindInternal = "internal";
const static std::string sSpanKindServer = "server";
const static std::string sSpanKindClient = "client";
const static std::string sSpanKindProducer = "producer";
const static std::string sSpanKindConsumer = "consumer";
const static std::string sSpanKindUnknown = "unknown";

const std::string& GetKindString(SpanEvent::Kind kind) {
    switch (kind) {
        case SpanEvent::Kind::Unspecified:
            return sSpanKindUnspecified;
        case SpanEvent::Kind::Internal:
            return sSpanKindInternal;
        case SpanEvent::Kind::Server:
            return sSpanKindServer;
        case SpanEvent::Kind::Client:
            return sSpanKindClient;
        case SpanEvent::Kind::Producer:
            return sSpanKindProducer;
        case SpanEvent::Kind::Consumer:
            return sSpanKindConsumer;
        default:
            return sSpanKindUnknown;
    }
}

} // namespace logtail
